import sqlparser
from dbexceptions import DatabaseError

class DBLogger(object):

    def __init__(self, log_path):
        import logging
        logger = logging.getLogger('FileSystemStorage')
        handler = logging.FileHandler(log_path)
        formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        logger.setLevel(logging.WARNING)
        self.logger = logger

class Connection(object):
    pass

class Cursor(object):

    def __init__(self, db_instance):
        self.conn = db_instance
        self.results = None
        self.resultlist = None
        self.statement = None

    def description(self):
        pass

    def rowcount(self):
        pass

    def callproc(self, procname, **kw):
        pass

    def close(self):
        self.conn = None

    def execute(self, statement):
        '''
        'operation' is going to be a SQL command in most cases.
        '''
        import os

        success = 0
        dbpath = ''
        dbname = ''

        # the database may not have existed until now,
        # so try to setup logging
        self.conn.attemptLoggerSetup()

        # check that there is something to execute, and parse it
        if not statement and not self.statement:
            raise DatabaseError, 'cannot execute; statement not bound'
        if not statement:
            statement = self.statement
        p = sqlparser.SQLParser(statement)

        # the fss object does all the work on the file system
        fss = FileSystemStorage(self.conn)
       
        # database type info
        if p.tokens.type[0] == 'DATABASE':
            dbpath = p.tokens.path[0]
            dbname = p.tokens.id[0]

        # check for create
        if p.tokens.command[0] == 'create':
            # check for create type
            if dbpath and dbname:
                if os.path.join(dbpath, dbname) != self.conn.database:
                    raise DatabaseError, 'cannot create an unbound database'
                fss.createDatabase()
            elif p.tokens.type[0] == 'TABLE':
                pass

        # check for drop
        elif p.tokens.command[0] == 'drop':
            # check for drop type
            if dbpath and dbname:
                if os.path.join(dbpath, dbname) != self.conn.database:
                    raise DatabaseError, 'cannot drop an unbound database'
                fss.dropDatabase()

        # check for select

        # check for insert

        # check for alter


    def executemany(self, operation, seq_of_parameters):
        pass

    def fetchone(self):
        return self.results[0]

    def fetchmany(self, size=None):
        return self.results[0:size]

    def fetchall(self):
        return self.results

    def rowcount(self):
        return len(self.results)

    def nextset(self):
        pass

    def arraysize(self):
        pass

    def setinputsizes(self, sizes=()):
        pass

    def setoutputsize(self, size=(), column=None):
        pass


class fssdb(Connection):
    '''
    A database is typically another separate piece of software, but in this
    case, we are actually simulating a database by creating a layer ontop of the
    file system. fssdb is the database that our drivers will interface with.
    fssdb is what actually uses our SQL parser converting SQL queries into
    python commands that execute policy on the file system.

    Check the connection dictionry and keyword parameters:
    >>> import filesystem
    >>> conn_dict = {'host':'localhost', 'name':'test_db', 'username':None, 'password':None, 'path':'/tmp'}
    >>> db = filesystem.fssdb(conn_dict, test='yay-hoos')
    >>> db.__dict__['test']
    Traceback (most recent call last):
    KeyError: 'test'
    >>> db.__dict__['host']
    'localhost'
    >>> db.__dict__['path']
    '/tmp'
    >>> db.__dict__['name']
    'TEST_DB'

    Test for unbound database:
    >>> db = filesystem.fssdb()
    >>> c = db.cursor()
    Traceback (most recent call last):
        raise DatabaseError, 'Not bound to database'
    DatabaseError: Not bound to database

    Get a cursor:
    >>> conn_dict = {'path':'/tmp', 'name':'test_db'}
    >>> db = filesystem.fssdb(conn_dict)
    >>> curs = db.cursor()

    Create a database:
    >>> sql = "CREATE DATABASE '%s' %s" % (db.path, db.name)
    >>> print sql
    CREATE DATABASE '/tmp' TEST_DB
    >>> curs.execute(sql)

    Test the brains behind the SELECT query:
    >>> fss = filesystem.FileSystemStorage(db)
    >>> fss._findDirectory(entity_type='database')
    ['/tmp/TEST_DB']

    Delete the database:
    >>> sql = "DROP DATABASE '%s' %s" % (db.path, db.name) 
    >>> print sql
    DROP DATABASE '/tmp' TEST_DB
    >>> curs.execute(sql)

    Try to create a bogus database:
    >>> conn_dict = {'path':'/zz123ss_tmp', 'name':'test_db'}
    >>> db = filesystem.fssdb(conn_dict)
    >>> curs = db.cursor()
    >>> sql = "CREATE DATABASE '%s' %s" % (db.path, db.name)
    >>> print sql
    CREATE DATABASE '/zz123ss_tmp' TEST_DB
    >>> curs.execute(sql)
    Traceback (most recent call last):
        raise DatabaseError, 'path to database does not exist'
    DatabaseError: path to database does not exist

    '''
    def __init__(self, *arg, **kw):
        import os

        # dsn format:       dbtype://user:pass@host:port/database?<options>
        # fssdb over ssh:   fssdb://user:pass@host/database?path=<some path>
        # local fssdb:      fssdb://database?path=<some path>
        #
        # XXX need to figure out a nice way to create and parse dsns
        dsn = ''
        self.dsn = 'fssdb://%s' % dsn

        if arg and type(arg[0]).__name__ == 'dict':
            kw.update(arg[0])

        kw_defaults = ['user', 'pass', 'host', 'database', 'path', 'name', 
            'verbose', 'autocommit']
        for key in kw_defaults:
            if kw.has_key(key):
                val = kw[key]
            else:
                val = ''
            setattr(self, key, val)
            
        self.verbose = bool(self.verbose)
        self.autocommit = bool(self.autocommit)
        self.closed = True
        self.transaction_open = False
        if not self.database and (self.path and self.name):
            self.database = os.path.join(self.path, self.name.upper())
        else:
            self.path, self.name = os.path.split(self.database)
        self.name = self.name.upper()

        self.logfile = 'fssdb.log'
        self.log = None
        self.attemptLoggerSetup()
    

    def start(self):
        pass

    def open(self):
        pass

    def close(self):
        if self.closed: return

    def commit(self):
        pass

    def rollback(self):
        pass

    def cursor(self):
        if not self.database:
            raise DatabaseError, 'Not bound to database'
        return Cursor(self)    

    def attemptLoggerSetup(self):
        import os

        success = 0
        db = self.database
        logfile = os.path.join(self.database, self.logfile)

        # check if database exists
        if os.path.isdir(db):
            # check if logfile exists
            if not os.path.isfile(logfile):
                fh = open(logfile, 'w')
                fh.write('')
                fh.close()
            log = DBLogger(logfile)
            self.log = log.logger
            success = 1
        
class FileSystemStorage(object):

    def __init__(self, connection):
        self.conn = connection
        self.metafile = '.properties'
        self.sectionname = 'metadata'

    def _createDir(self, path):
        pass

    def _createIniFile(self, path, name='.properties'):
        pass

    def _readIniFile(self, path, name='.properties'):
        pass
    
    def _findDirectory(self, dir_name='', entity_type='', entity_name=''):
        '''
        dir_name - the actual name of the directory on the file system
        entity_type - database, table, field; this information is stored 
            in the .properties file in each directory as the 'type' option
        entity_name - this is stored in the .properties file as the 'name'
            option

        The database path will be recursed, searching for entities that
        match the parameters passed.
        '''
        import os
        import ConfigParser

        matches = []
        debug = []
        db = self.conn.database
        sec = self.sectionname
        dirwalk = list(os.walk(db))
        if not dirwalk:
            dirwalk = ((db, '', ''), )
        #raise str(dirwalk)
        for fullpath, dirs, files in dirwalk:
            prop = os.path.join(fullpath, self.metafile)
            config = ConfigParser.ConfigParser()
            config.read(prop)
            debug.append((fullpath, dirs, files))
            debug.append(config.__dict__)
            if dir_name in dirs:
                matches.append(os.path.join(fullpath, dir_name))
            if config.has_option(sec, 'type') or config.has_option(sec, 'name'):
                if config.get(sec, 'type') == entity_type:
                    debug.append('found type "%s" in %s' % (entity_type, prop))
                    matches.append(fullpath)
                if config.get(sec, 'name') == entity_name:
                    debug.append('found name "%s" in %s' % (entity_name, prop))
                    matches.append(fullpath)
        
        #raise str(debug)
        return matches
        
    def _writeIniFile(self, path, data):
        '''
        This method requires the full path to the 
        metadata file as well as the data dictionary
        that contains the info for creating the ini
        metadata file.
        '''
        import ConfigParser

        prop = ConfigParser.ConfigParser()
        section = data['section']
        prop.add_section(section)
        for key, val in data['options'].items():
            prop.set(section, key, val)
        fh = open(path, 'w')
        prop.write(fh)
        

    def createDatabase(self, description=''):
        '''
        This method creates the database within which
        tables and fields will be created. The database is
        actually a directory with a metadata file in it.
        '''
        import os

        db = self.conn.database
        if not os.path.isdir(self.conn.path):
            raise DatabaseError, 'path to database does not exist'
        if os.path.isdir(db):
            raise DatabaseError, 'database name at path already exists'

        # create the db dir and metadata file
        os.mkdir(db)
        prop = os.path.join(db, self.metafile)
        options = {'type': 'database', 'name': self.conn.name, 'description': description}
        data = {
            'section': self.sectionname,
            'options': options,
        }
        self._writeIniFile(prop, data)

    def dropDatabase(self):
        '''
        This method removes the database and all of its
        subdirectories.
        '''
        import os
        import shutil

        # XXX this is not compatible with non-UNIX systems
        shutil.rmtree(self.conn.database)
        

    def createTable(self, table, description=''):
        entitytype = 'table'
        name = table

    def createFields(self, row_list):
        '''
        'row_list' is actually a list of dicts; each dict is a data
        structure representing the metadata for that field.
        '''
        pass

    def createField(self, field_dict):
        pass

    def getDatabaseName(self):
        pass

    def getTableName(self):
        pass

    def getFieldName(self):
        pass

    def getBlobFileType(self):
        pass


def _test():
    import doctest, filesystem
    return doctest.testmod(filesystem)

if __name__ == '__main__':
    _test()

